/* Copyright (c) 2008 Google Inc.
*  Copyright (c) 2009 Pierre Henri Kuate.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !DONT_USE_OAUTH_NET
using OAuth.Net.Common;
using OAuth.Net.Components;
#endif

namespace org.opensocial.client
{
    /// <summary> A utility object containing several static methods for signing requests in
    /// accordance with the OAuth specification -- methods generate a signature,
    /// and append it and other required parameters to the URL associated with
    /// the OpenSocialHttpRequest argument. The signatures are then validated
    /// by the container to verify that they were submitted from a trusted application.
    /// </summary>
    /// <author>  Jason Cooper
    /// </author>
    public static class OpenSocialOAuthClient
    {
        /// <summary> Extracts pertinent properties from passed OpenSocialClient object and
        /// passes these along with OpenSocialHttpRequest object to a separate
        /// method which does the actual signing.
        /// </summary>
        /// <param name="request">OpenSocialHttpRequest object which contains both the URL
        /// to sign as well as the POST body which must be included as a
        /// parameter when signing POST requests
        /// </param>
        /// <param name="client">OpenSocialClient object with various properties, both
        /// optional and required, used during the signing process
        /// </param>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public static void signRequest(OpenSocialHttpRequest request, OpenSocialClient client)
        {
            string token = client.getProperty(OpenSocialClient.Properties.TOKEN);
            string viewerId = client.getProperty(OpenSocialClient.Properties.VIEWER_ID);
            string consumerKey = client.getProperty(OpenSocialClient.Properties.CONSUMER_KEY);
            string consumerSecret = client.getProperty(OpenSocialClient.Properties.CONSUMER_SECRET);

            string accessToken = client.getProperty(OpenSocialClient.Properties.ACCESS_TOKEN);
            string accessTokenSecret = client.getProperty(OpenSocialClient.Properties.ACCESS_TOKEN_SECRET);

            signRequest(request, token, viewerId, consumerKey, consumerSecret, accessToken, accessTokenSecret);
        }

        /*// <summary> Adds optional query string parameters to request URL if present, then
        /// passes the passed OpenSocialHttpRequest to a separate method which
        /// does the actual signing.
        /// </summary>
        /// <param name="request">OpenSocialHttpRequest object which contains both the URL
        /// to sign as well as the POST body which must be included as a
        /// parameter when signing POST requests
        /// </param>
        /// <param name="token">Security token which can be borrowed from a running gadget
        /// and appended to the URL as a query string parameter instead of
        /// signing the request. These types of tokens are typically short-
        /// lived and must be refreshed often, making signing much more
        /// practical.
        /// </param>
        /// <param name="viewerId">ID of the user currently using the application (or for
        /// whom the application is making requests on behalf of). The ID
        /// is appended to the URL as a query string parameter so it can
        /// be signed with the rest of the URL.
        /// </param>
        /// <param name="consumerKey">Application key assigned and used by containers to
        /// uniquely identify applications
        /// </param>
        /// <param name="consumerSecret">Secret key shared between application owner and
        /// container. Used to generate the signature which is attached to
        /// the request so containers can verify the authenticity of the
        /// requests made by the client application.
        /// </param>
        /// <param name="accessToken">Used in 3 legged OAuth. The user's access token.</param>
        /// <param name="accessTokenSecret">Used in 3 legged OAuth. The user's access token secret.</param>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public static void signRequest(OpenSocialHttpRequest request, string token, string viewerId, string consumerKey, // TODO: Remove
                                       string consumerSecret, string accessToken, string accessTokenSecret)
        {
            OpenSocialUrl requestUrl = request.getUrl();

            if (viewerId != null)
            {
                requestUrl.addQueryStringParameter("xoauth_requestor_id", viewerId);
            }
            if (token != null)
            {
                requestUrl.addQueryStringParameter("st", token);
            }

            signRequest(request, consumerKey, consumerSecret, accessToken, accessTokenSecret);
        }*/

        /// <summary> Signs the URL associated with the passed request object using the passed
        /// consumer key and secret in accordance with the OAuth specification and
        /// appends signature and other required parameters to the URL as query
        /// string parameters.
        /// </summary>
        /// <param name="request">OpenSocialHttpRequest object which contains both the URL
        /// to sign as well as the POST body which must be included as a
        /// parameter when signing POST requests
        /// </param>
        /// <param name="token">Security token which can be borrowed from a running gadget
        /// and appended to the URL as a query string parameter instead of
        /// signing the request. These types of tokens are typically short-
        /// lived and must be refreshed often, making signing much more
        /// practical.
        /// </param>
        /// <param name="viewerId">ID of the user currently using the application (or for
        /// whom the application is making requests on behalf of). The ID
        /// is appended to the URL as a query string parameter so it can
        /// be signed with the rest of the URL.
        /// </param>
        /// <param name="consumerKey">Application key assigned and used by containers to
        /// uniquely identify applications
        /// </param>
        /// <param name="consumerSecret">Secret key shared between application owner and
        /// container. Used to generate the signature which is attached to
        /// the request so containers can verify the authenticity of the
        /// requests made by the client application.
        /// </param>
        /// <param name="accessToken">Used in 3 legged OAuth. The user's access token.</param>
        /// <param name="accessTokenSecret">Used in 3 legged OAuth. The user's access token secret.</param>
        /// <throws>  OAuthException </throws>
        /// <throws>  IOException </throws>
        /// <throws>  URISyntaxException </throws>
        public static void signRequest(OpenSocialHttpRequest request, string token, string viewerId,
            string consumerKey, string consumerSecret,
            string accessToken, string accessTokenSecret)
        {
            if (string.IsNullOrEmpty(consumerKey) || string.IsNullOrEmpty(consumerSecret))
                throw new OpenSocialException("Consumer key and secret are required");

            OpenSocialUrl requestUrl = request.getUrl();

            if (token != null)
                requestUrl.addQueryStringParameter("st", token);

            var requestUri = new System.Uri(requestUrl.ToString());
            var requestMethod = request.Method;
            var requestBody = request.Body;
            //string encodedRequestBody;

            var timestamp = UnixTime.ToUnixTime(System.DateTime.Now).ToString();
            var nonce = System.DateTime.Now.Ticks.ToString();

            string signatureBase;
            string normalizedRequestParameters = ComputeRequestParameters(
                requestUri, requestUrl.QueryStringParameters, requestMethod, requestBody, viewerId,
                consumerKey, consumerSecret, accessToken, accessTokenSecret,
                "1.0", timestamp, nonce, "HMAC-SHA1", out signatureBase);

            foreach (var p in normalizedRequestParameters.Split('&'))
            {
                var parts = p.Split('=');
                if (parts.Length != 2)
                    throw new OpenSocialRequestException("Invalid parameter");
                var key = parts[0];
                var value = parts[1];
                //if (key != encodedRequestBody) // TODO: Not required!?! When using RPC, it seems that the body must be added
                if (!requestUrl.QueryStringParameters.ContainsKey(key))
                    requestUrl.addQueryStringParameter(key, value);
            }
        }


        public static string ComputeRequestParameters(System.Uri requestUri,
            System.Collections.Generic.IDictionary<string, string> queryStringParameters,
            string requestMethod, string requestBody, string viewerId,
            string consumerKey, string consumerSecret,
            string accessToken, string accessTokenSecret,
            string oauthVersion, string timestamp, string nonce, string signatureMethod,
            out string signatureBase)
        {
#if !DONT_USE_OAUTH_NET
            var parameters = new OAuthParameters();
            if (viewerId != null)
                parameters.AdditionalParameters.Add("xoauth_requestor_id", viewerId);
            parameters.ConsumerKey = consumerKey;
            parameters.Nonce = nonce;
            parameters.SignatureMethod = signatureMethod;
            parameters.Timestamp = timestamp.ToString(System.Globalization.CultureInfo.InvariantCulture);
            parameters.Version = oauthVersion;
            if (accessToken != null)
            {
                parameters.Token = accessToken;
                parameters.TokenSecret = accessTokenSecret;
            }
            //else parameters.Token = string.Empty; // Note: If this is added, OAuthParameters must not ignore empty parameters

            if (requestBody != null)
                parameters.AdditionalParameters.Add(requestBody, string.Empty);
            //encodedRequestBody = Rfc3986.Encode(requestBody);

            if (queryStringParameters != null)
                foreach (var query in queryStringParameters)
                    parameters.AdditionalParameters.Add(query.Key, query.Value);

            ISigningProvider provider = new HmacSha1SigningProvider(); //ServiceProviderContext.GetSigningProvider(parameters.SignatureMethod)
            if (provider.SignatureMethod != parameters.SignatureMethod)
                throw new System.ArgumentException("SignatureMethod not supported", "signatureMethod");

            signatureBase = SignatureBase.Create(requestMethod, requestUri, parameters);
            var signature = provider.ComputeSignature(signatureBase, consumerSecret, accessTokenSecret);

            parameters.Signature = signature;

            return parameters.ToQueryStringFormat();
#else // Use OAuth.OAuthBase // TODO: Not working yet
            if (viewerId != null)
                requestUrl.addQueryStringParameter("xoauth_requestor_id", viewerId);

            var oAuth = new OAuth.OAuthBase();

            var timestamp = oAuth.GenerateTimeStamp();
            var nonce = oAuth.GenerateNonce();

            string normalizedUrl;
            var providedSignature = oAuth.GenerateSignature(requestTokenBaseUri,
                consumerKey, consumerSecret, accessToken, accessTokenSecret,
                requestMethod, timestamp, nonce, OAuth.OAuthBase.SignatureTypes.HMACSHA1,
                out normalizedUrl, out normalizedRequestParameters);

            requestUrl.addQueryStringParameter("oauth_signature", providedSignature);
#endif
        }
    }
}